package com.egg.mall.service.sku.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.egg.common.core.exception.CustomException;
import com.egg.common.mybatis.service.impl.BaseServiceImpl;
import com.egg.common.redis.util.LockUtil;
import com.egg.mall.entity.sku.Sku;
import com.egg.mall.entity.sku.SkuSnapshot;
import com.egg.mall.entity.spu.Spu;
import com.egg.mall.mapper.sku.SkuSnapshotMapper;
import com.egg.mall.service.sku.ISkuService;
import com.egg.mall.service.sku.ISkuSnapshotService;
import com.egg.mall.service.spu.ISpuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.Objects;
import java.util.Optional;


/**
 * @author east
 * @since 2022-08-27
 */
@Slf4j
@Service
public class SkuSnapshotServiceImpl extends BaseServiceImpl<SkuSnapshotMapper, SkuSnapshot> implements ISkuSnapshotService {


    @Autowired
    private ISkuService skuService;
    @Autowired
    private ISpuService spuService;


    /**
     * 分布式锁key
     */
    private static final String REDIS_SKU_SNAPSHOT_GET_KEY = "sku-snapshot-key:";

    /**
     * 构建分布式锁key
     *
     * @param skuId
     * @param version
     * @return
     */
    private static String buildRedisKey(Long skuId, Long version) {
        return REDIS_SKU_SNAPSHOT_GET_KEY + skuId + ":" + version;
    }

    @Override
    public SkuSnapshot get(Long skuId, Long snapshotVersion) {
        Optional<SkuSnapshot> skuSnapshotOpt = find(skuId, snapshotVersion);
        return skuSnapshotOpt.orElseGet(() -> get(skuId, snapshotVersion, 0));
    }

    private SkuSnapshot get(Long skuId, Long snapshotVersion, Integer count) {
        if (ObjectUtil.isEmpty(skuId) || ObjectUtil.isEmpty(snapshotVersion)) {
            throw new CustomException("获取SKU快照,必填参数缺失");
        }
        if (count > 50) {
            try {
                // 循环超过50次，休息一下
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
        Optional<SkuSnapshot> skuSnapshotOne = find(skuId, snapshotVersion);
        if (skuSnapshotOne.isPresent()) {
            return skuSnapshotOne.get();
        }
        if (count > 100) {
            // 循环超过100次，抛出异常
            throw new CustomException("获取SKU快照,服务异常");
        }
        String tryLockKey = buildRedisKey(skuId, snapshotVersion);
        // 防止redis出现问题, 一直无法获取锁
        count++;
        boolean tryLock = LockUtil.tryLock(tryLockKey);
        if (!tryLock) {
            // 自旋获取快照
            return get(skuId, snapshotVersion, count);
        }
        // 拿到锁的线程，才有资格新增
        Optional<SkuSnapshot> skuSnapshotTwo = find(skuId, snapshotVersion);
        return skuSnapshotTwo.orElseGet(() -> {
            try {
                return add(skuId, snapshotVersion);
            } catch (DuplicateKeyException e) {
                // 这里一般不可能出现重复, 因为前面已经添加了分布式锁,除非redis出现问题.
                return get(skuId, snapshotVersion);
            }
        });
    }

    private Optional<SkuSnapshot> find(Serializable skuId, Long snapshotVersion) {
        if (ObjectUtil.isEmpty(skuId) || ObjectUtil.isEmpty(snapshotVersion)) {
            return Optional.empty();
        }
        LambdaQueryWrapper<SkuSnapshot> qw = buildLambdaSortQw();
        qw.eq(SkuSnapshot::getSkuId, skuId)
                .eq(SkuSnapshot::getSkuVersion, snapshotVersion);
        qw.last("LIMIT 1");
        return Optional.ofNullable(getOne(qw));
    }

    private SkuSnapshot add(Long skuId, Long version) {
        if (ObjectUtil.isEmpty(skuId) || ObjectUtil.isEmpty(version)) {
            throw new CustomException("获取SKU快照,必填参数缺失");
        }
        Sku sku = skuService.baseGetById(skuId);
        if (!Objects.equals(sku.getSnapshotVersion(), version)) {
            throw new CustomException("SKU发生变动,请重新刷新");
        }
        Spu spu = spuService.baseGetById(sku.getSpuId());
        SkuSnapshot skuSnapshot = new SkuSnapshot();
        skuSnapshot.setSkuId(skuId);
        skuSnapshot.setSkuVersion(version);
        skuSnapshot.setSpuName(spu.getName());
        skuSnapshot.setSkuName(sku.getName());
        skuSnapshot.setSkuPrice(sku.getPrice());
        skuSnapshot.setSkuPicUrl(sku.getPicUrl());
        baseSave(skuSnapshot);
        return skuSnapshot;
    }


}
